package com.example.complier;

import com.example.annotation.BindView;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;

public class BindViewProcessor extends AbstractProcessor {
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        HashSet<String> set = new HashSet<>();
        set.add(BindView.class.getCanonicalName());
        return set;
    }
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
        parseBindView(roundEnv);
        return true;
    }

    public void parseBindView(RoundEnvironment roundEnv) {

        Set<Element> bindViewElementSet = (Set<Element>) roundEnv.getElementsAnnotatedWith(BindView.class);
        buildBindClass(bindViewElementSet);
    }

    public void buildBindClass(Set<Element> eleSet) {
        Map<TypeElement, ArrayList<Element>> groupedElement = groupingElementWithType(eleSet);
        Set<TypeElement> keySet = groupedElement.keySet();
        for (TypeElement classItem : keySet) {
            TypeSpec.Builder typeBuilder = makeTypeSpecBuilder(classItem);
            MethodSpec.Builder constructorBuilder = makeConstructor(classItem);
            buildConstructorCode(constructorBuilder, groupedElement.get(classItem));
            typeBuilder.addMethod(constructorBuilder.build());
            JavaFile file = JavaFile.builder(getPackageName(classItem), typeBuilder.build())
                    .build();
            try {
                file.writeTo(this.processingEnv.getFiler());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public Map<TypeElement, ArrayList<Element>> groupingElementWithType(Set<Element> eleSet) {
        Map<TypeElement, ArrayList<Element>> groupedElement = new HashMap<>();

        for (Element item : eleSet) {
            checkAnnotationLegal(item);
            TypeElement enclosingElement = (TypeElement) item.getEnclosingElement();
            if (groupedElement.keySet().contains(enclosingElement)) {
                groupedElement.get(enclosingElement).add(item);
            } else {
                ArrayList list = new ArrayList<Element>();
                list.add(item);
                groupedElement.put(enclosingElement, list);
            }
        }
        return groupedElement;
    }

    public void checkAnnotationLegal(Element ele) {
        if (ele.getKind() != ElementKind.FIELD) {
            throw new RuntimeException("@BindView must in filed! $ele kind is ${ele.kind}");
        }
        Set<Modifier> modifier = ele.getModifiers();
        if (modifier.contains(Modifier.FINAL)) {
            throw new RuntimeException("@BindView filed can not be final!");
        }
        if (modifier.contains(Modifier.PRIVATE)) {
            throw new RuntimeException("@BindView filed can not be private");
        }
    }

    private TypeSpec.Builder makeTypeSpecBuilder(TypeElement typeEle) {
        String format = String.format("%s_ViewBinding", typeEle.getSimpleName());
        return TypeSpec.classBuilder(format)
                .addModifiers(Modifier.PUBLIC);
    }

    private MethodSpec.Builder makeConstructor(TypeElement typeElement) {
        TypeMirror typeMirror = typeElement.asType();
        return MethodSpec.constructorBuilder()
                .addModifiers(Modifier.PUBLIC)
                .addParameter(TypeName.get(typeMirror), "target");
    }

    private String getPackageName(Element typeElement) {
        Element ele = typeElement;
        while (ele.getKind() != ElementKind.PACKAGE) {
            ele = ele.getEnclosingElement();
        }
        return ((PackageElement) ele).getQualifiedName().toString();
    }

    private void buildConstructorCode(MethodSpec.Builder bindMethodBuilder, ArrayList<Element> elements) {
        if (elements != null) {
            for (Element itemView : elements) {
                bindMethodBuilder.addStatement(String.format("target.%s = target.findViewById(%s)",itemView.toString(),itemView.getAnnotation(BindView.class).value()));
            }
        }
    }
}
